package com.gandong8.trace.tool.core.sql;


import java.lang.reflect.Field;
import java.sql.Statement;

import com.baomidou.mybatisplus.core.MybatisParameterHandler;
import com.gandong8.trace.tool.core.SystemClock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;

/**
 * 若是基于xml（mybatis-config.xml）配置：<plugin interceptor="com.gandong8.trace.web.core.SqlInterceptor"/>
 *
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
//@Component
@Slf4j
public class SqlInterceptor implements Interceptor {

    private Configuration configuration = null;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = SystemClock.now();
        Object target = invocation.getTarget();
        StatementHandler statementHandler = (StatementHandler) target;
        try {
            return invocation.proceed();
        } finally {
            setConfiguration(statementHandler);
            long endTime = SystemClock.now();
            long cost = endTime - startTime;
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = parseSql(boundSql);
            log.error("Sql cost={};sql={}", cost, sql);
        }
    }

    private void setConfiguration(StatementHandler statementHandler) {
        if(configuration == null){
            MybatisParameterHandler parameterHandler = (MybatisParameterHandler) statementHandler.getParameterHandler();
            Field configurationField = ReflectionUtils.findField(parameterHandler.getClass(), "configuration");
            assert configurationField != null;
            ReflectionUtils.makeAccessible(configurationField);
            try {
                this.configuration =  (Configuration) configurationField.get(parameterHandler);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }


    private String parseSql(BoundSql boundSql) {
        if (configuration == null) {
            return "";
        }
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        String sql = boundSql.getSql();
        if (sql == null || sql.length() == 0) {
            return "";
        }
        sql = sql.replaceAll("[\\s\n ]+", " ");
        if(CollectionUtils.isEmpty(parameterMappings)) {
            //没有参数
            return sql;
        }
        Object value;
        for (ParameterMapping parameterMapping : parameterMappings) {
            if(ParameterMode.OUT.equals(parameterMapping.getMode())) {
                return sql;
            }
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            TypeHandler typeHandler = parameterMapping.getTypeHandler();
            JdbcType jdbcType = parameterMapping.getJdbcType();
            if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
            }
            if(value == null) {
                continue;
            }
            String valueString = "";
            if (value instanceof String) {
                valueString = "'" + value + "'";
            } else if (value instanceof Date) {
                valueString = "'" + DateFormatUtils.format((Date) value,"yyyy-MM-dd HH:mm:ss.SSS") + "'";
            } else {
                valueString = value.toString();
            }
            sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(valueString));
        }
        return sql;
    }


    @Override
    public Object plugin(Object target) {
        //MybatisParameterHandler DefaultResultSetHandler RoutingStatementHandler
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }


}
